This is a series of note translators for SunVox that lock your notes into a specific scale.
It requires SunVox 1.9.3 or higher to use, as it makes use of MultiSynth event output.
Load the sunsynth for the scale you'd like to use.
Send notes to it, via a tracker, MIDI, or from another module.
Connect the scale's output to the input of a synth.
The notes sent to the synth will be locked into the scale you loaded.
musthe provides a basic music theory engine. We maintain fork in the same directory as this notebook.
Radiant Voices lets us construct and write SunVox compatible project files.
The code below goes through all notes and scales available in musthe, and creates the structures needed to lock notes into those scales.
In [1]:
import musthe as m
import rv.api as rv
In [2]:
def scale_project(root_note, scale_name):
name = '{} {}'.format(root_note, scale_name).replace('_', ' ')
filename = '{}.sunsynth'.format(name)
scale = m.scale(m.Note(root_note), scale_name=scale_name)
scale_ids = sorted({n.note_id for n in scale})
proj = rv.Project()
proj.name = name
in_mod = proj.new_module(rv.m.MultiSynth, name='I {}'.format(name))
in_mod.color = (0, 255, 0)
out_mod = proj.output
gate_mods = {}
metamod = rv.m.MetaModule(project=proj)
metamod.input_module = in_mod.index
metamod.name = proj.name
synth = rv.Synth(metamod)
def gate_mod(offset):
if offset not in gate_mods:
gate = gate_mods[offset] = proj.new_module(
rv.m.MultiSynth,
name='G{} {}'.format(offset, name),
)
gate.color = (64, 64, 64)
if offset > 0:
transpose = proj.new_module(
rv.m.MultiSynth,
name='T{} {}'.format(offset, name),
transpose=-offset,
)
transpose.color = (64, 64, 64)
in_mod >> gate >> transpose >> out_mod
else:
in_mod >> gate >> out_mod
for x in range(128):
gate.nv_curve.values[x] = 0
return gate_mods[offset]
# Start one octave below to ensure offset is initialized
# once we get to the note range we care about.
offset = 0
for note in range(-12, 128):
if note % 12 in scale_ids:
offset = 0
else:
offset += 1
gate = gate_mod(offset)
if note >= 0:
gate.nv_curve.values[note] = 255
proj.in_mod = in_mod
proj.layout(prog='dot')
return synth, filename
In [3]:
all_scales = rv.Project()
all_scales_mod = rv.m.MetaModule(project=all_scales)
all_scales_synth = rv.Synth(module=all_scales_mod)
all_scales_transposed = rv.Project()
all_scales_transposed_mod = rv.m.MetaModule(project=all_scales_transposed)
all_scales_transposed_synth = rv.Synth(module=all_scales_transposed_mod)
x_scale = 64
y_scale = 64
for x, root_note in enumerate([
'C',
'C#',
'Db',
'D',
'D#',
'Eb',
'E',
'F',
'F#',
'Gb',
'G',
'G#',
'Ab',
'A',
'A#',
'Bb',
'B',
], 1):
root_note_id = m.Note(root_note).note_id
for y, scale_name in enumerate([
'major',
'natural_minor',
'harmonic_minor',
'melodic_minor',
'dorian',
'locrian',
'lydian',
'mixolydian',
'phrygian',
'major_pentatonic',
'minor_pentatonic',
], 1):
try:
synth, filename = scale_project(root_note, scale_name)
filename = filename.replace('#', 's')
with open(filename, 'wb') as f:
synth.write_to(f)
transposed = None
if root_note_id:
filename = 'Transposed to C - {}'.format(filename)
transposed = synth.clone()
transposed.module.name = 'T {}'.format(synth.module.name)
in_mod = transposed.module.project.modules[synth.module.project.in_mod.index]
in_mod.transpose = -root_note_id
with open(filename, 'wb') as f:
transposed.write_to(f)
def wire(mod, project):
project += mod
project.output << mod
mod.x = project.output.x + (x * x_scale)
mod.y = project.output.y + (y * y_scale)
wire(synth.module, all_scales)
if transposed:
wire(transposed.module, all_scales_transposed)
except Exception as e:
print('*** :-(', root_note, scale_name, e)
else:
print(' :-)', synth.module.name)
with open('All Scales.sunsynth', 'wb') as f:
all_scales_synth.write_to(f)
print(' :-) All Scales')
with open('All Scales Transposed to C.sunsynth', 'wb') as f:
all_scales_transposed_synth.write_to(f)
print(' :-) All Scales Transposed to C')